/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.idea.internal.makeBackup

import com.intellij.compiler.server.BuildManager
import com.intellij.history.core.RevisionsCollector
import com.intellij.history.integration.LocalHistoryImpl
import com.intellij.history.integration.patches.PatchCreator
import com.intellij.ide.actions.RevealFileAction
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vcs.changes.Change
import com.intellij.util.WaitForProgressToShow
import com.intellij.util.io.ZipUtil
import org.jetbrains.kotlin.idea.KotlinJvmBundle
import org.jetbrains.kotlin.idea.util.application.runReadAction
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.ZipOutputStream

class CreateIncrementalCompilationBackup : AnAction(KotlinJvmBundle.message("create.backup.for.debugging.kotlin.incremental.compilation")) {
    companion object {
        const val BACKUP_DIR_NAME = ".backup"
        const val PATCHES_TO_CREATE = 5

        const val PATCHES_FRACTION = .25
        const val LOGS_FRACTION = .05
        const val PROJECT_SYSTEM_FRACTION = .05
        const val ZIP_FRACTION = 1.0 - PATCHES_FRACTION - LOGS_FRACTION - PROJECT_SYSTEM_FRACTION
    }

    override fun actionPerformed(e: AnActionEvent) {
        val project = e.project!!
        val projectBaseDir = File(project.baseDir!!.path)
        val backupDir = File(FileUtil.createTempDirectory("makeBackup", null), BACKUP_DIR_NAME)

        ProgressManager.getInstance().run(
            object : Task.Backgroundable(
                project,
                KotlinJvmBundle.message("creating.backup.for.debugging.kotlin.incremental.compilation"),
                true
            ) {
                override fun run(indicator: ProgressIndicator) {
                    createPatches(backupDir, project, indicator)
                    copyLogs(backupDir, indicator)
                    copyProjectSystemDir(backupDir, project, indicator)

                    zipProjectDir(backupDir, project, projectBaseDir, indicator)
                }
            }
        )
    }

    private fun createPatches(backupDir: File, project: Project, indicator: ProgressIndicator) {
        runReadAction {
            val localHistoryImpl = LocalHistoryImpl.getInstanceImpl()!!
            val gateway = localHistoryImpl.gateway!!
            val localHistoryFacade = localHistoryImpl.facade

            val revisionsCollector = RevisionsCollector(
                localHistoryFacade,
                gateway.createTransientRootEntry(),
                project.baseDir!!.path,
                project.locationHash,
                null
            )

            var patchesCreated = 0

            val patchesDir = File(backupDir, "patches")
            patchesDir.mkdirs()

            val revisions = revisionsCollector.result!!
            for (rev in revisions) {
                val label = rev.label
                if (label != null && label.startsWith(HISTORY_LABEL_PREFIX)) {
                    val patchFile = File(patchesDir, label.removePrefix(HISTORY_LABEL_PREFIX) + ".patch")

                    indicator.text = KotlinJvmBundle.message("creating.patch.0", patchFile)
                    indicator.fraction = PATCHES_FRACTION * patchesCreated / PATCHES_TO_CREATE

                    val differences = revisions[0].getDifferencesWith(rev)!!
                    val changes = differences.map { d ->
                        Change(d.getLeftContentRevision(gateway), d.getRightContentRevision(gateway))
                    }

                    PatchCreator.create(project, changes, patchFile.toPath(), false, null)

                    if (++patchesCreated >= PATCHES_TO_CREATE) {
                        break
                    }
                }
            }
        }
    }

    private fun copyLogs(backupDir: File, indicator: ProgressIndicator) {
        indicator.text = KotlinJvmBundle.message("copying.logs")
        indicator.fraction = PATCHES_FRACTION

        val logsDir = File(backupDir, "logs")
        FileUtil.copyDir(File(PathManager.getLogPath()), logsDir)

        indicator.fraction = PATCHES_FRACTION + LOGS_FRACTION
    }

    private fun copyProjectSystemDir(backupDir: File, project: Project, indicator: ProgressIndicator) {
        indicator.text = KotlinJvmBundle.message("copying.project.s.system.dir")
        indicator.fraction = PATCHES_FRACTION

        val projectSystemDir = File(backupDir, "project-system")
        FileUtil.copyDir(BuildManager.getInstance().getProjectSystemDirectory(project)!!, projectSystemDir)

        indicator.fraction = PATCHES_FRACTION + LOGS_FRACTION + PROJECT_SYSTEM_FRACTION
    }

    private fun zipProjectDir(backupDir: File, project: Project, projectDir: File, indicator: ProgressIndicator) {
        // files and relative paths

        val files = ArrayList<Pair<File, String>>() // files and relative paths
        var totalBytes = 0L

        for (dir in listOf(projectDir, backupDir.parentFile!!)) {
            FileUtil.processFilesRecursively(
                dir,
                /*processor*/ {
                    if (it!!.isFile
                        && !it.name.endsWith(".hprof")
                        && !(it.name.startsWith("make_backup_") && it.name.endsWith(".zip"))
                    ) {

                        indicator.text = KotlinJvmBundle.message("scanning.project.dir.0", it)

                        files.add(Pair(it, FileUtil.getRelativePath(dir, it)!!))
                        totalBytes += it.length()
                    }
                    true
                },
                /*directoryFilter*/ {
                    val name = it!!.name
                    name != ".git" && name != "out"
                }
            )
        }


        val backupFile = File(projectDir, "make_backup_" + SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(Date()) + ".zip")


        val zos = ZipOutputStream(FileOutputStream(backupFile))

        var processedBytes = 0L

        zos.use {
            for ((file, relativePath) in files) {
                indicator.text = KotlinJvmBundle.message("adding.file.to.backup.0", relativePath)
                indicator.fraction = PATCHES_FRACTION + LOGS_FRACTION + processedBytes.toDouble() / totalBytes * ZIP_FRACTION

                ZipUtil.addFileToZip(zos, file, relativePath, null, null)

                processedBytes += file.length()
            }
        }

        FileUtil.delete(backupDir)

        WaitForProgressToShow.runOrInvokeLaterAboveProgress(
            {
                RevealFileAction.showDialog(
                    project,
                    KotlinJvmBundle.message("successfully.created.backup.0", backupFile.absolutePath),
                    KotlinJvmBundle.message("created.backup"),
                    backupFile,
                    null
                )
            }, null, project
        )
    }
}
